/*
 
 RFA Manager, Refractor2 Archive editor.
 Copyright (C) 2008
 Torsten Kammer         torsten@ferroequinologist.de
 
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 */

#import "MyDocument.h"

#import "RFAArchive.h"
#import "RFATreeNode.h"

#import "ImageAndTextCell.h"
#import "RFABrowserCell.h"
#import "FileSizeFormatter.h"

#import "RFAImageViewingController.h";
#import "RFADragAndDropDelegate.h"
#import "RFATextEditorController.h"

#import "PropertyCategories.h"
#import "NSStringRFAAdditions.h"
#import "NSArrayRFAAdditions.h"

@interface MyDocument ()

- (void)_updateStatusBarText;
- (void)_updateSearchPathControl;
- (BOOL)_findLaunchAttributes;
- (void)_openFile:(id)sender;
- (void)_openFileSearch:(id)sender;
- (void)_openFiles:(NSArray *)files;

// Extracting files.
- (void)_extractFilesPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo;

// Adding new files.
- (void)_addFilesPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode forParent:(RFATreeNode *)contextInfo;
- (void)_addFilesToCurrentNode;
- (void)_addNodesToCurrentNode;

- (void)_startReplacementDialogueForFileName:(NSString *)name isDirectory:(BOOL)isDir continueSelector:(SEL)selector;
- (void)_fileReplacementDialogDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo;

@end

@implementation MyDocument

+ (NSSet *)keyPathsForValuesAffectingTabToShow
{
	return [NSSet setWithObject:@"viewMode"];
}

@synthesize archive, browser, foundEntries, searchArrayController, saveAndRunEnabled=canRunFile, searchPathControl, searchView, outlineView, viewMode;

- (id)init
{
    self = [super init];
    if (self) {
    
        // Add your subclass-specific initialization here.
        // If an error occurs here, send a [self release] message and return nil.
        
		archive = [[RFAArchive alloc] init];
		archive.root.undoManager = self.undoManager;
    }
    return self;
}

- (void)makeWindowControllers
{
	NSWindowController *mainWindowController = [[NSWindowController alloc] initWithWindowNibName:@"MyDocument" owner:self];
	[self addWindowController:mainWindowController];
	mainWindowController.shouldCloseDocument = YES;
}

- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
    [super windowControllerDidLoadNib:aController];
    
	if ([aController isKindOfClass:[RFATextEditorController class]]) return; // RFATextEditorController knows very well what it's doing on it's own, thank you very much.
	if ([aController isKindOfClass:[RFAImageViewingController class]]) return; // RFAImageViewingController knows very well what it's doing on it's own as well.
	
	[browser setCellClass:[RFABrowserCell class]];
	[browser loadColumnZero]; // Needed to make it use RFABrowserCell for the first entry.
	
	self.browser.doubleAction = @selector(_openFile:);
	self.browser.target = self;
	
	self.outlineView.doubleAction = @selector(_openFile:);
	self.outlineView.target = self;
	
	self.searchView.doubleAction = @selector(_openFileSearch:);
	self.searchView.target = self;
	
	outlineViewDelegate = [[RFADragAndDropDelegate alloc] initWithDocument:self];
    
    searchPathControl.pathComponentCells = [NSArray array];
	
    NSSearchFieldCell *searchCell = search.cell;
    searchCell.cancelButtonCell.target = self;
    searchCell.cancelButtonCell.action = @selector(cancelSearch:);
    
    [self willChangeValueForKey:@"tabToShow"];
    [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:0] forKey:@"defaultViewMode"]];
    viewMode = [[NSUserDefaults standardUserDefaults] integerForKey:@"defaultViewMode"];
    viewModeControl.selectedSegment = viewMode;
    inSearchMode = NO;
    [self didChangeValueForKey:@"tabToShow"];
	
	[fileTreeController addObserver:self forKeyPath:@"selection" options:NSKeyValueObservingOptionNew context:fileTreeController];
	[fileTreeController addObserver:self forKeyPath:@"arrangedObjects" options:NSKeyValueObservingOptionNew context:fileTreeController];
    [searchArrayController addObserver:self forKeyPath:@"selectedObjects" options:NSKeyValueObservingOptionNew context:searchArrayController];
    [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"searchFilenames" options:NSKeyValueObservingOptionNew context:[NSUserDefaults standardUserDefaults]];
    [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"searchFileContents" options:NSKeyValueObservingOptionNew context:[NSUserDefaults standardUserDefaults]];
}

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    // Checking for context because NSDocument may have other reasons to observe standardUserDefaults. Or not. We'll never know.
    if (context == [NSUserDefaults standardUserDefaults])
        [self updateSearchPredicate];
    else if (context == searchArrayController)
    {
        [self willChangeValueForKey:@"canExtract"];
        [self willChangeValueForKey:@"canDelete"];
        [self willChangeValueForKey:@"canAddFiles"];
        [self willChangeValueForKey:@"canMakeNewFolder"];
        [self _updateSearchPathControl];
        [self didChangeValueForKey:@"canExtract"];
        [self didChangeValueForKey:@"canDelete"];
        [self didChangeValueForKey:@"canAddFiles"];
        [self didChangeValueForKey:@"canMakeNewFolder"];
	}
    else if (context == fileTreeController)
	{
        [self willChangeValueForKey:@"canExtract"];
        [self willChangeValueForKey:@"canDelete"];
        [self willChangeValueForKey:@"canAddFiles"];
        [self willChangeValueForKey:@"canMakeNewFolder"];
        [self _updateStatusBarText];
        [self didChangeValueForKey:@"canExtract"];
        [self didChangeValueForKey:@"canDelete"];
        [self didChangeValueForKey:@"canAddFiles"];
        [self didChangeValueForKey:@"canMakeNewFolder"];
    }
    else
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}


- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
	return [archive completeFileData];
}

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
    archive = [[RFAArchive alloc] initWithData:data error:outError];
    if (!archive) return NO;
	
	archive.root.undoManager = self.undoManager;
    
	[self willChangeValueForKey:@"saveAndRunEnabled"];
	canRunFile = [self _findLaunchAttributes];
	[self didChangeValueForKey:@"saveAndRunEnabled"];
	
    return YES;
}

- (IBAction)createNewFolder:(id)sender;
{
	if ((inSearchMode && (self.searchArrayController.selectedObjects.count == 0 || [self.searchArrayController.selectedObjects.firstObject isLeaf])) || (!inSearchMode && fileTreeController.selectedNodes.count == 0))
	{
		NSBeep();
		return;
	}
	
	RFATreeNode *parent = nil;
	
	if (inSearchMode) parent = self.searchArrayController.selectedObjects.firstObject;
	else if ([fileTreeController.selectedObjects.firstObject isLeaf]) parent = [(RFATreeNode *) (fileTreeController.selectedObjects.firstObject) parent];
	else parent = (RFATreeNode *) fileTreeController.selectedObjects.firstObject;
	
	NSString *name = NSLocalizedString(@"Unnamed", @"name for new folder");
	NSUInteger number = 0;
	while ([parent hasChildWithName:name isDirectory:NULL])
	{
		number++;
		name = [NSString stringWithFormat:NSLocalizedString(@"Unnamed %lu", @"name for new folder if one with same name already exists"), (unsigned long) number];
	}
	[[parent mutableArrayValueForKey:@"children"] addObject:[[RFATreeNode alloc] initAsDirectory:name parent:parent]];
	
	if (inSearchMode) [self updateSearchPredicate];
}

- (IBAction)deleteItems:(id)sender;
{
	if (inSearchMode)
	{
		for (RFATreeNode *node in self.searchArrayController.selectedObjects)
			[node.parent removeChildNamed:node.name];
		
		[self updateSearchPredicate];
		return;
	}
	
	if (!fileTreeController.canRemove)
	{
		NSBeep();
		return;
	}
		
	@try
	{
		[fileTreeController remove:sender];
	}
	@catch (NSException *e)
	{
		// Most likely caused by trying to delete the root object. We cannot allow that.
		NSBeep();
	}
}

- (IBAction)addFiles:(id)sender;
{
	if ((inSearchMode && (self.searchArrayController.selectedObjects.count == 0 || [self.searchArrayController.selectedObjects.firstObject isLeaf])) || (!inSearchMode && fileTreeController.selectedNodes.count == 0))
	{
		NSBeep();
		return;
	}
	
	RFATreeNode *parent = nil;
	
	if (inSearchMode) parent = self.searchArrayController.selectedObjects.firstObject;
	else if ([fileTreeController.selectedObjects.firstObject isLeaf]) parent = [(RFATreeNode *) (fileTreeController.selectedObjects.firstObject) parent];
	else parent = (RFATreeNode *) fileTreeController.selectedObjects.firstObject;
	
	NSOpenPanel *panel = [NSOpenPanel openPanel];
	panel.allowsMultipleSelection = YES;
	panel.canChooseFiles = YES;
	panel.canChooseDirectories = YES;
	
	[panel beginSheetForDirectory:nil file:nil types:nil modalForWindow:self.windowForSheet modalDelegate:self didEndSelector:@selector(_addFilesPanelDidEnd:returnCode:forParent:) contextInfo:parent];
}

- (IBAction)extractFiles:(id)sender;
{
	NSOpenPanel *panel = [NSOpenPanel openPanel];
	panel.canChooseDirectories = YES;
	panel.canChooseFiles = NO;
	panel.allowsMultipleSelection = NO;
	
	[panel beginSheetForDirectory:nil file:nil types:nil modalForWindow:self.windowForSheet modalDelegate:self didEndSelector:@selector(_extractFilesPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
}

- (IBAction)run:(id)sender
{
	if (!canRunFile) return;
	
	NSArray *arguments;
//TODO: Hack because save and run doesn't work correctly when trying to load a map.
//	if (mapName)
//		arguments = [NSArray arrayWithObjects:@"+game", modName, @"+level", mapName, @"+restart", @"1", @"+hostServer", @"1", @"+gamePlayMode", @"Conquest", @"+getContentCRC", @"1", nil];
//	else
		arguments = [NSArray arrayWithObjects:@"+game", modName, @"+restart", @"1", nil];
	
	[NSTask launchedTaskWithLaunchPath:gamePath arguments:arguments];
}

- (IBAction)saveAndRun:(id)sender;
{
	if (!canRunFile) return;
	
	[self saveDocument:self];
	[self run:self];
}

- (IBAction)selectDisplayMode:(id)sender;
{
    [self willChangeValueForKey:@"tabToShow"];
    viewMode = [sender selectedSegment];
    [[NSUserDefaults standardUserDefaults] setInteger:viewMode forKey:@"defaultViewMode"];
    [self didChangeValueForKey:@"tabToShow"];
}
- (IBAction)search:(id)sender;
{
    if (!inSearchMode)
    {
        [self willChangeValueForKey:@"tabToShow"];
        inSearchMode = YES;
		viewModeControl.enabled = NO;
        [viewModeControl setEnabled:NO];
        [self didChangeValueForKey:@"tabToShow"];
    }
    
    [self updateSearchPredicate];
}
- (IBAction)cancelSearch:(id)sender;
{
    [self willChangeValueForKey:@"tabToShow"];
    inSearchMode = NO;
	viewModeControl.enabled = YES;
    search.stringValue = @"";
    [self didChangeValueForKey:@"tabToShow"];
}

@dynamic tabToShow;
- (NSInteger)tabToShow
{
    if (inSearchMode) return 2;
    return viewMode;
}

- (void)updateSearchPredicate
{
    [self willChangeValueForKey:@"foundEntries"];
	NSPredicate *filterPredicate = nil;
    BOOL searchFilenames = [[NSUserDefaults standardUserDefaults] boolForKey:@"searchFilenames"];
    BOOL searchFileContents = [[NSUserDefaults standardUserDefaults] boolForKey:@"searchFileContents"];
    NSString *searchTerm = search.stringValue;
    if (searchTerm.length == 0 || (!searchFilenames && !searchFileContents))
        filterPredicate = [NSPredicate predicateWithValue:NO];
	else if (searchFilenames && searchFileContents)
		filterPredicate = [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@ OR contents CONTAINS[cd] %@", searchTerm, searchTerm];
	else if (searchFilenames)
		filterPredicate = [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", searchTerm];
	else if (searchFileContents)
		filterPredicate = [NSPredicate predicateWithFormat:@"contents CONTAINS[cd] %@", searchTerm];
	foundEntries = [archive.root nodesMatchingPredicate:filterPredicate];

    [self didChangeValueForKey:@"foundEntries"];
}

- (void)_updateSearchPathControl;
{
    NSArray *selected = searchArrayController.selectedObjects;
    if (selected.count != 1) 
        searchPathControl.pathComponentCells = [NSArray array];
    else
    {
		NSArray *pathComponents = [selected.firstObject valueForKeyPath:@"fullPath.pathComponents"];
		NSMutableArray *pathComponentCells = [NSMutableArray array];
		NSImage *folderImage = [[NSWorkspace sharedWorkspace] iconForFile:@"/System/Library"];
		for (NSString *component in pathComponents)
		{
			if ([component isEqual:@"/"]) continue;
			NSPathComponentCell *cell = [[NSPathComponentCell alloc] initTextCell:component];
			cell.image = folderImage;
			cell.controlSize = NSSmallControlSize;
			cell.font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]];
			[pathComponentCells addObject:cell];
		}
		if ([selected.firstObject isLeaf])
			[pathComponentCells.lastObject setImage:[[NSWorkspace sharedWorkspace] iconForFileType:[pathComponentCells.lastObject stringValue]]];
		searchPathControl.pathComponentCells = pathComponentCells;
    }
	[self _updateStatusBarText];
}

- (BOOL)_findLaunchAttributes;
{
	if (!self.fileURL.isFileURL) return NO;
	NSArray *pathComponents = self.fileURL.path.pathComponents;
	NSUInteger length = pathComponents.count;

	mapName = nil;
	modName = nil;
	gamePath = nil;
	
	// There are three cases: Either it's in Archives, or in Archives/bf1942, or in Archvies/bf1942/levels
	// First: Is it in levels?
	NSString *folder = [pathComponents objectAtIndex:length - 2];
	if ([folder isEqual:@"levels"])
	{
		if (length < 7) return NO;
		mapName = [pathComponents objectAtIndex:length - 1];
		mapName = [mapName substringToIndex:[mapName length] - 4];
		if (![[pathComponents objectAtIndex:length - 3] isCaseInsensitiveEqual:@"bf1942"]) return NO;
		if (![[pathComponents objectAtIndex:length - 4] isCaseInsensitiveEqual:@"Archives"]) return NO;
		modName = [pathComponents objectAtIndex:length - 5];
		if (![[pathComponents objectAtIndex:length - 6] isCaseInsensitiveEqual:@"Mods"]) return NO;
		if (![[pathComponents objectAtIndex:length - 7] isCaseInsensitiveEqual:@"Data"]) return NO;
		NSArray *resultArray = [[pathComponents subarrayWithRange:NSMakeRange(0, length - 7)] arrayByAddingObjectsFromArray:[NSArray arrayWithObjects:@"Battlefield 1942.app", @"Contents", @"MacOS", @"Battlefield 1942", nil]];
		gamePath = [NSString pathWithComponents:resultArray];
		return [[NSFileManager defaultManager] fileExistsAtPath:gamePath];
	}
	else if ([folder isEqual:@"bf1942"])
	{
		if (length < 6) return NO;
		if (![[pathComponents objectAtIndex:length - 3] isCaseInsensitiveEqual:@"Archives"]) return NO;
		modName = [pathComponents objectAtIndex:length - 4];
		if (![[pathComponents objectAtIndex:length - 5] isCaseInsensitiveEqual:@"Mods"]) return NO;
		if (![[pathComponents objectAtIndex:length - 6] isCaseInsensitiveEqual:@"Data"]) return NO;
		NSArray *resultArray = [[pathComponents subarrayWithRange:NSMakeRange(0, length - 6)] arrayByAddingObjectsFromArray:[NSArray arrayWithObjects:@"Battlefield 1942.app", @"Contents", @"MacOS", @"Battlefield 1942", nil]];
		gamePath = [NSString pathWithComponents:resultArray];
		return [[NSFileManager defaultManager] fileExistsAtPath:gamePath];
	}
	else if ([folder isEqual:@"Archives"])
	{
		if (length < 5) return NO;
		modName = [pathComponents objectAtIndex:length - 3];
		if (![[pathComponents objectAtIndex:length - 4] isCaseInsensitiveEqual:@"Mods"]) return NO;
		if (![[pathComponents objectAtIndex:length - 5] isCaseInsensitiveEqual:@"Data"]) return NO;
		NSArray *resultArray = [[pathComponents subarrayWithRange:NSMakeRange(0, length - 5)] arrayByAddingObjectsFromArray:[NSArray arrayWithObjects:@"Battlefield 1942.app", @"Contents", @"MacOS", @"Battlefield 1942", nil]];
		gamePath = [NSString pathWithComponents:resultArray];
		return [[NSFileManager defaultManager] fileExistsAtPath:gamePath];
	}
	return NO;
}

- (void)_openFile:(id)sender;
{
	[self _openFiles:[fileTreeController valueForKeyPath:@"selectedObjects"]];
}
- (void)_openFileSearch:(id)sender;
{
	[self _openFiles:[searchArrayController valueForKeyPath:@"selectedObjects"]];
}
- (void)_openFiles:(NSArray *)files;
{
	for (RFATreeNode *node in files)
	{
		// See whether there is already a window controller open for this node.
		NSWindowController *controller = nil;
		for (NSWindowController *curentController in self.windowControllers)
		{
			if (!([curentController isKindOfClass:[RFAImageViewingController class]] || [curentController isKindOfClass:[RFATextEditorController class]])) continue;
			if (((RFAImageViewingController *) controller).archiveEntry == node)
			{
				controller = curentController;
				break;
			}
		}
        if (controller)
        {
            [[controller window] makeKeyAndOrderFront:self];
            continue;
        }
        
        if (node.isText)
		{
			RFATextEditorController *textController = [[RFATextEditorController alloc] initWithArchiveEntry:node];
			[self addWindowController:textController];
			[[textController window] makeKeyAndOrderFront:self];
		}
        else if (node.isImage)
        {
            RFAImageViewingController *viewController = [[RFAImageViewingController alloc] initWithArchiveEntry:node];
			[self addWindowController:viewController];
			[[viewController window] makeKeyAndOrderFront:self];
        }
	}
}

- (void)_updateStatusBarText
{
	if (inSearchMode)
	{
		if (searchArrayController.selectedObjects.count == 0)
		{
			if ([searchArrayController.arrangedObjects count] == 1)
				statusBar.stringValue = [NSString stringWithFormat:NSLocalizedString(@"1 object, %@", @"status bar for no selection, single object"), [FileSizeFormatter stringForNumber:[searchArrayController valueForKeyPath:@"arrangedObjects.@sum.data.length"]]];
			else
				statusBar.stringValue = [NSString stringWithFormat:NSLocalizedString(@"%lu objects, %@", @"status bar for no selection, multiple objects"), (unsigned long) [searchArrayController.arrangedObjects count], [FileSizeFormatter stringForNumber:[searchArrayController valueForKeyPath:@"arrangedObjects.@sum.data.length"]]];
		}
		else if (searchArrayController.selectedObjects.count == 1)
			statusBar.stringValue = [NSString stringWithFormat:NSLocalizedString(@"1 object of %lu selected, %@", @"status bar for single selection"), (unsigned long) [searchArrayController.arrangedObjects count], [FileSizeFormatter stringForNumber:[searchArrayController valueForKeyPath:@"selectedObjects.@sum.data.length"]]]; 
		else
			statusBar.stringValue = [NSString stringWithFormat:NSLocalizedString(@"%lu objects of %lu selected, %@", @"status bar for multiple selection"), (unsigned long) searchArrayController.selectedObjects.count, (unsigned long) [searchArrayController.arrangedObjects count], [FileSizeFormatter stringForNumber:[searchArrayController valueForKeyPath:@"selectedObjects.@sum.data.length"]]];
	}
	else
	{
		if (fileTreeController.selectedNodes.count == 0)
			statusBar.stringValue = [NSString stringWithFormat:NSLocalizedString(@"1 object, %@", @"status bar for no selection, single object"), [FileSizeFormatter stringForNumber:[self valueForKeyPath:@"archive.root.fullSize"]]];
		else if (fileTreeController.selectedNodes.count == 1)
		{
			if ([fileTreeController.selectedObjects.firstObject isLeaf])
				statusBar.stringValue = [NSString stringWithFormat:NSLocalizedString(@"1 object of %lu selected, %@", @"status bar for single selection"), (unsigned long) [[[[fileTreeController.selectedNodes objectAtIndex:0] parentNode] childNodes] count], [FileSizeFormatter stringForNumber:[fileTreeController valueForKeyPath:@"selectedObjects.@sum.data.length"]]]; 
			else
			{
				if ([fileTreeController.selectedNodes.firstObject childNodes].count == 1)
					statusBar.stringValue = [NSString stringWithFormat:NSLocalizedString(@"1 object, %@", @"status bar for no selection, single object"), [FileSizeFormatter stringForNumber:[fileTreeController valueForKeyPath:@"selectedObjects.@sum.data.size"]]];
				else
					statusBar.stringValue = [NSString stringWithFormat:NSLocalizedString(@"%lu objects, %@", @"status bar for no selection, multiple objects"), (unsigned long) [fileTreeController.selectedNodes.firstObject childNodes].count, [FileSizeFormatter stringForNumber:[fileTreeController valueForKeyPath:@"selectedObjects.@sum.data.length"]]];
			}
		}
		else
			statusBar.stringValue = [NSString stringWithFormat:NSLocalizedString(@"%lu objects of %lu selected, %@", @"status bar for multiple selection"), (unsigned long) fileTreeController.selectedNodes.count, (unsigned long) [fileTreeController.selectedNodes.firstObject parentNode].childNodes.count, [FileSizeFormatter stringForNumber:[fileTreeController valueForKeyPath:@"selectedObjects.@sum.data.length"]]];
	}
}

- (void)_addFilesPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode forParent:(RFATreeNode *)parent;
{
	if (returnCode == NSCancelButton) return;
	
	// All files will be in the same directory, so we can safely make assumptions about the name.
	[self startAddingFilesWithNames:panel.filenames toNode:parent atIndex:[parent countOfChildren]];
}

- (void)_extractFilesPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo;
{
	if (returnCode == NSCancelButton) return;
	
	if (inSearchMode)
		[self namesForFiles:searchArrayController.selectedObjects createdAt:panel.filenames.firstObject];
	else
		[self namesForFiles:fileTreeController.selectedObjects createdAt:panel.filenames.firstObject];
}

#pragma mark -
#pragma mark Adding/Copying/Moving files

- (void)startAddingFilesWithNames:(NSArray *)names toNode:(RFATreeNode *)node atIndex:(NSUInteger)index;
{
	replaceAll = NO;
	leaveAll = NO;
	replaceFirst = NO;
	
	nodeToInsertTo = node;
	newFileIndex = index;
	
	filesToInsert = names;
	
	[self _addFilesToCurrentNode];
}

- (void)startCoypingNodes:(NSArray *)nodes toNode:(RFATreeNode *)node atIndex:(NSUInteger)index;
{
	replaceAll = NO;
	leaveAll = NO;
	replaceFirst = NO;
	
	nodeToInsertTo = node;
	newFileIndex = index;
	
	filesToInsert = [NSMutableArray array];
	
	for (RFATreeNode *node in nodes)
		[(NSMutableArray *) filesToInsert addObject:[node copyWithZone:nil]];
	
	[self _addNodesToCurrentNode];
}

- (void)startMovingNodes:(NSArray *)nodes toNode:(RFATreeNode *)parent atIndex:(NSUInteger)index;
{
	replaceAll = NO;
	leaveAll = NO;
	replaceFirst = NO;
	
	nodeToInsertTo = parent;
	newFileIndex = index;
	
	filesToInsert = [NSMutableArray array];
	
	for (RFATreeNode *node in nodes)
	{
		if ([parent isDescendantOf:node])
		{
			NSBeep();
			return;
		}
		
		[node.parent removeChildNamed:node.name];
		
		[(NSMutableArray *) filesToInsert addObject:node];
	}
	
	[self _addNodesToCurrentNode];
}

- (NSArray *)namesForFiles:(NSArray *)items createdAt:(NSString *)path
{
	NSMutableArray *result = [NSMutableArray array];
	for (RFATreeNode *node in items)
	{
		NSString *pathName = [path stringByAppendingPathComponent:node.name];
		if (node.isLeaf)
		{
			[result addObject:pathName];
			[node.data writeToFile:pathName atomically:YES];
		}
		else
		{
			BOOL isDir;
			if (![[NSFileManager defaultManager] fileExistsAtPath:pathName isDirectory:&isDir])
			{
				[[NSFileManager defaultManager] createDirectoryAtPath:pathName withIntermediateDirectories:NO attributes:nil error:nil];
			}
			[result addObjectsFromArray:[self namesForFiles:[node valueForKey:@"children"] createdAt:pathName]];
		}
	}
	
	return result;
}

#pragma mark -
#pragma mark Menu activation property

@dynamic canExtract, canDelete, canAddFiles, canMakeNewFolder;
-(BOOL)canExtract
{
    if (inSearchMode) return searchArrayController.selectedObjects.count != 0;
    else return fileTreeController.selectedObjects.count != 0;
}

-(BOOL)canDelete
{
    if (inSearchMode)
    {
        if (searchArrayController.selectedObjects.count == 0) return NO;
        if ([searchArrayController.selectedObjects.firstObject parent] == nil) return NO;
        return YES;
    }
    if (fileTreeController.selectedObjects.count == 0) return NO;
    if ([fileTreeController.selectedObjects.firstObject parent] == nil) return NO;
    return YES;
}

-(BOOL)canAddFiles
{
    if (inSearchMode)
    {
        if(searchArrayController.selectedObjects.count == 0) return NO;
        return ![searchArrayController.selectedObjects.firstObject isLeaf];
    }
    return fileTreeController.selectedObjects.count > 0;
}

-(BOOL)canMakeNewFolder
{
    if (inSearchMode)
    {
        if(searchArrayController.selectedObjects.count == 0) return NO;
        return ![searchArrayController.selectedObjects.firstObject isLeaf];
    }
    return fileTreeController.selectedObjects.count > 0;
}

#pragma mark -
#pragma mark Supporting methods for adding files

- (void)_addFilesToCurrentNode
{
	// The idea behind this weird architecture is that this method keeps adding files until it hits a conflict. Then it starts the document modal panel asking the user for his/her opinion and exists. The didEndSelector method will then call this method again, so it'll finish it's work. Not the most pretty thing, but it works.
	for (NSString *path in filesToInsert)
	{
		BOOL isDir;
		if ([nodeToInsertTo hasChildWithName:path.lastPathComponent isDirectory:&isDir])
		{
			if (leaveAll) continue;
			
			if (replaceAll || replaceFirst)
			{
				[nodeToInsertTo removeChildNamed:path.lastPathComponent];
				if (newFileIndex >= nodeToInsertTo.countOfChildren) newFileIndex = nodeToInsertTo.countOfChildren - 1;
			}
			else
			{
				// Prepare and run alert
				[self _startReplacementDialogueForFileName:path.lastPathComponent isDirectory:isDir continueSelector:_cmd];
				return;
			}
		}
		replaceFirst = NO;
		[[nodeToInsertTo mutableArrayValueForKey:@"children"] insertObject:[RFATreeNode nodeFromFile:path parent:nodeToInsertTo] atIndex:newFileIndex];
		newFileIndex++;
		filesToInsert = filesToInsert.arrayByRemovingFirstObject;
	}
	replaceFirst = NO;
	replaceAll = NO;
	leaveAll = NO;
	nodeToInsertTo = nil;
	filesToInsert = nil;
	newFileIndex = 0;
	
	if (inSearchMode) [self updateSearchPredicate];
}

- (void)_addNodesToCurrentNode;
{
	for (RFATreeNode *node in filesToInsert)
	{
		BOOL isDir;
		if ([nodeToInsertTo hasChildWithName:node.name isDirectory:&isDir])
		{
			if (leaveAll) continue;
			
			if (replaceAll || replaceFirst)
			{
				[nodeToInsertTo removeChildNamed:node.name];
				if (newFileIndex >= nodeToInsertTo.countOfChildren) newFileIndex = nodeToInsertTo.countOfChildren - 1;
			}
			else
			{
				// Prepare and run alert
				[self _startReplacementDialogueForFileName:node.name isDirectory:isDir continueSelector:_cmd];
				return;
			}
		}
		replaceFirst = NO;
		[[nodeToInsertTo mutableArrayValueForKey:@"children"] insertObject:node atIndex:newFileIndex];
		newFileIndex++;
		filesToInsert = filesToInsert.arrayByRemovingFirstObject;
	}
	replaceFirst = NO;
	replaceAll = NO;
	leaveAll = NO;
	nodeToInsertTo = nil;
	filesToInsert = nil;
	newFileIndex = 0;
	
	if (inSearchMode) [self updateSearchPredicate];
}

- (void)_startReplacementDialogueForFileName:(NSString *)name isDirectory:(BOOL)isDir continueSelector:(SEL)selector;
{
	NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"%@ already exists.", @"file replacing dialog title"), name] defaultButton:NSLocalizedString(@"Replace", @"file replacing dialog default option") alternateButton:NSLocalizedString(@"Keep", @"file replacing dialog alternate option") otherButton:nil informativeTextWithFormat:isDir ? NSLocalizedString(@"Do you wish to replace %@? All it's contents will be replaced with those of the new folder.", @"file replacing information for directories") :NSLocalizedString(@"Do you wish to replace %@ with the new file?", @"file replacing information for files"), name];
	if (filesToInsert.count > 1)
	{
		alert.showsSuppressionButton = YES;
		alert.suppressionButton.title = NSLocalizedString(@"Do for all files.", @"file replacing do-for-all button");
	}
	[alert beginSheetModalForWindow:self.windowForSheet modalDelegate:self didEndSelector:@selector(_fileReplacementDialogDidEnd:returnCode:contextInfo:) contextInfo:selector];
}

- (void)_fileReplacementDialogDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo;
{
	if (returnCode == NSAlertDefaultReturn)
	{
		replaceFirst = YES;
		if (alert.suppressionButton.state == NSOnState) replaceAll = YES;
	}
	else if (returnCode == NSAlertAlternateReturn)
	{
		if (alert.suppressionButton.state == NSOnState) leaveAll = YES;
		filesToInsert = filesToInsert.arrayByRemovingFirstObject;
	}
	
	[self performSelector:(SEL) contextInfo];
}

- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
    if ([menuItem action] == @selector(deleteItems:)) return self.canDelete;
    else if ([menuItem action] == @selector(createNewFolder:)) return self.canMakeNewFolder;
    else if ([menuItem action] == @selector(addFiles:)) return self.canAddFiles;
    else if ([menuItem action] == @selector(extractFiles:)) return self.canExtract;
    else if ([menuItem action] == @selector(run:)) return self.saveAndRunEnabled;
    else if ([menuItem action] == @selector(saveAndRun:)) return self.saveAndRunEnabled;
    else if ([menuItem action] == @selector(useBrowserView:)) return self.tabToShow < 2;
    else if ([menuItem action] == @selector(useListView:)) return self.tabToShow < 2;
    
    return [super validateMenuItem:menuItem];
}

@end
